/*
* #%L
* Lapis JSF Exporter Core
* %%
* Copyright (C) 2013 - 2015 Lapis Software Associates
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package com.lapis.jsfexporter;
import java.net.URLEncoder;
import javax.el.ELContext;
import javax.el.MethodExpression;
import javax.el.ValueExpression;
import javax.faces.FacesException;
import javax.faces.component.StateHolder;
import javax.faces.component.UIComponent;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.ActionEvent;
import javax.faces.event.ActionListener;
import com.lapis.jsfexporter.api.IExportType;
import com.lapis.jsfexporter.spi.IExportSource;
import com.lapis.jsfexporter.spi.IExportTypeFactory;
public class DataExporter implements ActionListener, StateHolder {
private ValueExpression source;
private ValueExpression sourceOptions;
private ValueExpression fileType;
private ValueExpression fileName;
private ValueExpression fileOptions;
private MethodExpression preProcessor;
private MethodExpression postProcessor;
public DataExporter() {}
public DataExporter(ValueExpression source, ValueExpression sourceOptions,
ValueExpression fileType, ValueExpression fileName,
ValueExpression fileOptions, MethodExpression preProcessor,
MethodExpression postProcessor) {
this.source = source;
this.sourceOptions = sourceOptions;
this.fileType = fileType;
this.fileName = fileName;
this.fileOptions = fileOptions;
this.preProcessor = preProcessor;
this.postProcessor = postProcessor;
}
@Override
public Object saveState(FacesContext context) {
Object[] values = new Object[7];
values[0] = source;
values[1] = sourceOptions;
values[2] = fileType;
values[3] = fileName;
values[4] = fileOptions;
values[5] = preProcessor;
values[6] = postProcessor;
return values;
}
@Override
public void restoreState(FacesContext context, Object state) {
Object[] values = (Object[]) state;
source = (ValueExpression) values[0];
sourceOptions = (ValueExpression) values[1];
fileType = (ValueExpression) values[2];
fileName = (ValueExpression) values[3];
fileOptions = (ValueExpression) values[4];
preProcessor = (MethodExpression) values[5];
postProcessor = (MethodExpression) values[6];
}
@Override
public boolean isTransient() {
return false;
}
@Override
public void setTransient(boolean newTransientValue) {}
@Override
public void processAction(ActionEvent event) throws AbortProcessingException {
try {
internalProcessAction(event);
} catch (Exception e) {
throw new FacesException(e);
}
}
private <TS extends UIComponent, CS, TT, CT> void internalProcessAction(ActionEvent event) throws Exception {
FacesContext facesContext = FacesContext.getCurrentInstance();
ExternalContext externalContext = facesContext.getExternalContext();
ELContext elContext = facesContext.getELContext();
// get the export source and retrieve source options
String componentId = (String) source.getValue(elContext);
TS sourceComponent = (TS) event.getComponent().findComponent(componentId);
if (sourceComponent == null) {
throw new FacesException("Could not find component \"" + componentId + "\" in view");
}
IExportSource<TS, CS> exportSource = (IExportSource<TS, CS>) ExportSourceFactory.getExportSource(facesContext, sourceComponent);
CS sourceOptionsValue;
if (sourceOptions == null) { // source options are not mandatory; get the defaults if not set
sourceOptionsValue = exportSource.getDefaultConfigOptions();
} else {
sourceOptionsValue = (CS) sourceOptions.getValue(elContext);
}
// get the export type factory and retrieve file options
String fileTypeValue = (String) fileType.getValue(elContext);
IExportTypeFactory<TT, CT, ?> exportTypeFactory = (IExportTypeFactory<TT, CT, ?>) ExportTypeFactoryFactory.getExportType(facesContext, fileTypeValue);
CT fileOptionsValue;
if (fileOptions == null) { // file options are not mandatory; get the defaults if not set
fileOptionsValue = exportTypeFactory.getDefaultConfigOptions();
} else {
fileOptionsValue = (CT) fileOptions.getValue(elContext);
}
// create a new exporter
IExportType<TT, CT, ?> exportType = exportTypeFactory.createNewExporter(fileOptionsValue);
TT exportContext = exportType.getContext();
// invoke the pre-processor if there is one
if (preProcessor != null) {
preProcessor.invoke(elContext, new Object[]{exportContext});
}
// generate the export
exportType.beginExport(exportSource.getColumnCount(sourceComponent, sourceOptionsValue));
exportSource.exportData(sourceComponent, sourceOptionsValue, exportType, facesContext);
exportType.endExport();
// invoke the post-processor if there is one
if (postProcessor != null) {
postProcessor.invoke(elContext, new Object[]{exportContext});
}
// configure response meta-data
externalContext.setResponseContentType(exportType.getContentType());
externalContext.setResponseHeader("Expires", "0");
externalContext.setResponseHeader("Cache-Control", "must-revalidate, post-check=0, pre-check=0");
externalContext.setResponseHeader("Pragma", "public");
/*
* filename* is the proper way to send non-ASCII filenames, but not all browsers support it (e.g. IE 8).
* So we also supply a normal filename encoded the same way, since that works on most browsers (but not Firefox).
* See RFC 6266 and http://greenbytes.de/tech/tc2231/
*/
String encodedFileName = URLEncoder.encode(fileName.getValue(elContext) + "." + exportType.getFileExtension(), "UTF-8");
externalContext.setResponseHeader("Content-Disposition", "attachment; filename=\"" + encodedFileName + "\"; filename*=UTF8''" + encodedFileName);
// write the response and signal JSF that we're done
exportType.writeExport(externalContext);
facesContext.responseComplete();
}
}